﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading;
using System.Windows.Forms;
using Common.Data;
using Common.Entities;
using Common.Entities.Items;
using Common.Enums;
using Common.Extensions;
using Common.Packets;
using Lidgren.Network;
using MasterServer.Common.Helpers;
using MasterServer.Entities;
using SamplesCommon;

namespace MasterServer
{
    public partial class Main : Form
    {
        private Process proc;
        private PerformanceCounter cpuCounter;
        private long maxMem = 0;
        private long minMem = long.MaxValue;
        private float maxCPU = 0;
        private float minCPU = float.MaxValue;
        public static NetServer Server;
        private static Stopwatch StopWatch = new Stopwatch();
        private bool _isExiting = false;
        public List<ServerEntity> Servers = new List<ServerEntity>();
        private Dictionary<string, string> _playerTokens = new Dictionary<string, string>();

        public List<long> MemoryValues = new List<long>();
        public List<float> CPUValues = new List<float>();

        public Main()
        {
            InitializeComponent();
            StopWatch.Start();
            proc = Process.GetCurrentProcess();
            cpuCounter = new PerformanceCounter("Process", "% Processor Time", proc.ProcessName, true);
            NetPeerConfiguration config = new NetPeerConfiguration("Master");
            config.Port = 14243;
            //config.SimulatedLoss = 0.02f;
            config.MaximumConnections = 25600;
            config.SetMessageTypeEnabled(NetIncomingMessageType.ConnectionApproval, true);
            Server = new NetServer(config);
            Server.RegisterReceivedCallback(new SendOrPostCallback(ServerLoop));
            Server.Start();
        }
        public void AddConsoleLog(string text, ConsoleMessageType messageType)
        {
            if (!_isExiting)
            {
                string log = string.Format("{0} {1} {2}", DateTime.Now,
                                           messageType.GetDescription(), text);
                try
                {

                    this.Invoke(
                        new Action(
                            () =>
                            {
                                NativeMethods.AppendText(logTb,
                                                         log);
                            }));

                }
                catch
                { }
                using (FileStream fs = new FileStream("Log.log", FileMode.Append, FileAccess.Write))
                {
                    using (StreamWriter sw = new StreamWriter(fs))
                    {
                        sw.WriteLine(log);
                        sw.Close();
                    }
                    fs.Close();
                }

            }
        }
        public void ServerLoop(object peer)
        {
            double nextSendUpdates = NetTime.Now;
            Player pl;
            NetIncomingMessage msg;
            while ((msg = Server.ReadMessage()) != null)
            {
                switch (msg.MessageType)
                {
                    case NetIncomingMessageType.VerboseDebugMessage:
                    case NetIncomingMessageType.DebugMessage:
                        AddConsoleLog(msg.ReadString(), ConsoleMessageType.Info);
                        break;
                    case NetIncomingMessageType.WarningMessage:
                        AddConsoleLog(msg.ReadString(), ConsoleMessageType.Warning);
                        break;
                    case NetIncomingMessageType.ErrorMessage:
                        AddConsoleLog(msg.ReadString(), ConsoleMessageType.Error);
                        break;
                    case NetIncomingMessageType.StatusChanged:
                        NetConnectionStatus status = (NetConnectionStatus)msg.ReadByte();
                        if (status == NetConnectionStatus.Connected)
                        {

                            AddConsoleLog(string.Format("New connection! {0}", msg.SenderConnection.RemoteEndpoint), ConsoleMessageType.Info);
                            ServerEntity serv = (ServerEntity)msg.SenderConnection.Tag;//добавляем в коллекцию сервер
                            serv.Connection = msg.SenderConnection;
                            Servers.Add(serv);
                            string str = serv.ServerType.GetDescription() + " " + serv.IP + ":" + serv.Port;
                            if (serv.ServerType == ServerType.GameServer)//если это гейм сервер, добавим перечисление карт
                            {
                                str += " ";
                                foreach (int id in serv.MapIds)
                                {
                                    str += id + ",";
                                }
                                if (str[str.Length - 1] == ',')
                                {
                                    str = str.Substring(0, str.Length - 1);
                                }
                            }
                            connectedLb.Items.Add(str);//добавим сервер в список для отображения
                        }
                        else if (status == NetConnectionStatus.Disconnected)
                        {
                            int idInList = Servers.FindIndex(p => p == ((ServerEntity)msg.SenderConnection.Tag));
                            if (idInList != -1)
                            {
                                connectedLb.Items.RemoveAt(idInList);
                                Servers.RemoveAt(idInList); //удаляем сервер из списков
                                AddConsoleLog(
                                    "Server " + ((ServerEntity)msg.SenderConnection.Tag).IP + ":" +
                                    ((ServerEntity)msg.SenderConnection.Tag).Port + " disconnected! (" + msg.ReadString() + ")",
                                    ConsoleMessageType.Info);
                            }
                        }

                        break;
                    case NetIncomingMessageType.ConnectionApproval:
                        try
                        {
                            //TODO: авторизация сервера по токену?
                            string token = msg.ReadString();
                            if (token != "SomePassword")
                            {
                                msg.SenderConnection.Deny("Bad approve data, go away!");
                                AddConsoleLog("Wrong server key from " + msg.SenderEndpoint.Address + ":" + msg.SenderEndpoint.Port, ConsoleMessageType.Warning);
                                return;
                            }
                            ServerType sType = (ServerType)msg.ReadInt32();
                            int port = msg.ReadInt32();
                            List<int> ids = null;
                            if (sType == ServerType.GameServer)
                            {
                                int elementsCount = msg.ReadInt32();
                                ids = new List<int>();
                                for (int i = 0; i < elementsCount; i++)
                                {
                                    ids.Add(msg.ReadInt32());
                                }
                            }
                            ServerEntity se = new ServerEntity() { IP = msg.SenderConnection.RemoteEndpoint.Address.ToString(), MapIds = ids, Port = port, ServerType = sType };
                            msg.SenderConnection.Tag = se;
                            msg.SenderConnection.Approve();
                        }
                        catch (NetException)
                        {
                            msg.SenderConnection.Deny("Bad approve data, go away!");
                            AddConsoleLog("Wrong server key from " + msg.SenderEndpoint.Address + ":" + msg.SenderEndpoint.Port, ConsoleMessageType.Warning);
                        }
                        break;
                    case NetIncomingMessageType.Data:
                        ServerPacketType spType = (ServerPacketType)msg.ReadInt16();
                        ProcessPacket(spType, msg);
                        break;
                }
            }
        }

        public void ProcessPacket(ServerPacketType serverPacketType, NetIncomingMessage msg)
        {
            switch (serverPacketType)
            {
                case ServerPacketType.GameServersList:
                    ProcessGameServerList(msg);
                    break;
                case ServerPacketType.Authorize:
                    ProcessAuthorization(msg);
                    break;
                case ServerPacketType.PlayerData:
                    //login = msg.ReadString();


                    break;
                case ServerPacketType.PlayerSelected:
                    PlayerSelectionProcess(msg);
                    break;
                case ServerPacketType.PlayerExit:
                    ProcessPlayerExit(msg);
                    break;
                case ServerPacketType.PlayerCharacterDeleted:
                    PlayerDeletingProcess(msg);
                    break;
            }
        }

        private void ProcessPlayerExit(NetIncomingMessage msg)
        {
            string player = msg.ReadString();
            SLDB db = new SLDB();
            if(_playerTokens.ContainsKey(player))
            {
                _playerTokens.Remove(player);
            }
            if ((msg.SenderConnection.Tag as ServerEntity).ServerType == ServerType.LoginServer)
            {
                players pl = db.players.FirstOrDefault(p => p.nick == player);
                if (pl != null)
                {
                    pl.online = false;
                    pl.accounts.online = false;
                    db.SaveChanges();
                }
                NetOutgoingMessage om = Server.CreateMessage();
                om.Write((short) ServerPacketType.PlayerExit);
                om.Write(player);
                om.Write(true);//кик со всех серверов
                Server.SendToAll(om, NetDeliveryMethod.ReliableOrdered);
            }
            else if ((msg.SenderConnection.Tag as ServerEntity).ServerType == ServerType.GameServer)
            {
                NetOutgoingMessage om = Server.CreateMessage();
                om.Write((short)ServerPacketType.ChangePlayerLocation);
                om.Write(player);
                om.Write(0);//перемещаем игрока в общую комнату чата
                Server.SendToAll(om, NetDeliveryMethod.ReliableOrdered);
            }
        }

        private void ProcessGameServerList(NetIncomingMessage msg)
        {
            NetOutgoingMessage om = Server.CreateMessage();
            om.Write((short)ServerPacketType.GameServersList);
            List<ServerEntity> tempServers = Servers.Where(p => p.ServerType == ServerType.GameServer).ToList();
            int serversCount = tempServers.Count;
            om.Write(serversCount);
            for (int i = 0; i < serversCount; i++)
            {
                using (MemoryStream ms = new MemoryStream())
                {
                    BinaryFormatter bf = new BinaryFormatter();
                    bf.Serialize(ms, tempServers[i]);
                    ms.Position = 0;
                    byte[] buffer = ms.ToArray();
                    om.Write(buffer.Length);
                    om.Write(buffer);
                }
            }
            msg.SenderConnection.SendMessage(om, NetDeliveryMethod.ReliableSequenced, 0);
        }

        private void ProcessAuthorization(NetIncomingMessage msg)
        {
            SLDB db = new SLDB();
            NetOutgoingMessage om;
            string login = msg.ReadString();
            string pass = msg.ReadString();
            pass = SecurityHelper.GetSecureDBHash(pass);
            accounts acc = db.accounts.FirstOrDefault(p => p.login == login && p.password == pass);
            om = Server.CreateMessage();
            om.Write((short)ServerPacketType.Authorize);
            om.Write(login);
            if (acc == null)
            {
                //возврат отказа

                om.Write(false);
                om.Write("Неверный пароль или аккаунт!");
            }
            else
            {
                if (!acc.@checked)
                {
                    //отказ, т.к. не подтвердил почту
                    om.Write(false);
                    om.Write("Подтвердите почту!");
                }
                else if (acc.online)
                {
                    om.Write(false);
                    om.Write("Аккаунт уже в сети!");
                }
                else
                {
                    //разрешить доступ
                    om.Write(true);
                    AccountInfo ai = new AccountInfo() { Points = acc.gold };
                    List<SlotInfo> slots = new List<SlotInfo>();
                    List<players> players = db.players.Where(p => p.acc_id == acc.id).ToList();
                    foreach (players player in players)
                    {
                        SlotInfo sl = new SlotInfo();
                        sl.Name = player.nick;
                        sl.Money = player.money;
                        sl.PlayTime = new TimeSpan(0, 0, player.playtime);
                        sl.Race = (byte)(player.race.HasValue ? player.race.Value : 0);
                        sl.AccountId = acc.id;
                        sl.Id = player.id;
                        slots.Add(sl);
                    }
                    ai.Slots = slots.ToArray();
                    using (MemoryStream ms = new MemoryStream())
                    {
                        BinaryFormatter bf = new BinaryFormatter();
                        bf.Serialize(ms, ai);
                        ms.Position = 0;
                        byte[] buffer = ms.ToArray();
                        om.Write(buffer.Length);
                        om.Write(buffer);
                    }
                    acc.online = true;
                    db.SaveChanges();
                    //отсылка данных о персонажах
                }
            }
            msg.SenderConnection.SendMessage(om, NetDeliveryMethod.ReliableSequenced, 0);
        }

        private void PlayerDeletingProcess(NetIncomingMessage msg)
        {
            SLDB db = new SLDB();
            string login = msg.ReadString();
            long charId = msg.ReadInt64();
            Player pl;
            accounts acc = db.accounts.FirstOrDefault(p => p.login == login);
            NetOutgoingMessage om = Server.CreateMessage();
            om.Write((short)ServerPacketType.PlayerCharacterDeleted);
            om.Write(login);
            if (acc != null)
            {
                players player = acc.players.FirstOrDefault(p => p.id == charId);
                if (player != null)
                {
                    om.Write(true);
                    om.Write(player.nick);
                    db.DeleteObject(player);
                    db.SaveChanges();
                    //Server.SendToAll(tokenMessage, NetDeliveryMethod.ReliableSequenced);//отсылаем всем серверам данные о входе игрока
                }
                else
                {
                    om.Write(false);
                }
            }
            else
            {
                om.Write(false);
            }
            msg.SenderConnection.SendMessage(om, NetDeliveryMethod.ReliableSequenced, 0);
        }

        private void PlayerSelectionProcess(NetIncomingMessage msg)
        {
            SLDB db = new SLDB();
            string login = msg.ReadString();
            long charId = msg.ReadInt64();
            Player pl;
            accounts acc = db.accounts.FirstOrDefault(p => p.login == login);
            NetOutgoingMessage om = Server.CreateMessage();
            om.Write((short)ServerPacketType.PlayerSelected);
            om.Write(login);
            if (acc != null)
            {
                players player = acc.players.FirstOrDefault(p => p.id == charId);
                if (player != null)
                {
                    Guid token = Guid.NewGuid();
                    _playerTokens.Add(player.nick, token.ToString());
                    player.online = true;

                    om.Write(true);
                    pl = new Player();
                    pl.Token = token;
                    pl.Race = player.race.HasValue ? player.race.Value : 0;
                    pl.PlayTime = new TimeSpan(0, 0, player.playtime);
                    pl.Nick = player.nick;
                    om.Write(pl.Nick);
                    pl.Money = player.money;
                    pl.Lvl = player.level;
                    pl.Kills = player.kills.HasValue ? player.kills.Value : 0;
                    pl.Hp = player.hp;
                    pl.GreifPoints = player.greif_points.HasValue ? player.greif_points.Value : 0;
                    pl.Exp = player.exp;
                    pl.Deaths = player.deaths.HasValue ? player.deaths.Value : 0;
                    pl.TextureId = player.texture_id;
                    if (player.armor != null)
                    {
                        byte[] buffer = player.armor;
                        using (MemoryStream ms = new MemoryStream())
                        {
                            BinaryFormatter bf = new BinaryFormatter();
                            ms.Write(buffer, 0, buffer.Length);
                            ms.Position = 0;
                            Armor armor = (Armor)bf.Deserialize(ms);
                            pl.CurrentArmor = armor;
                        }
                    }
                    if (player.weapon != null)
                    {
                        byte[] buffer = player.weapon;
                        using (MemoryStream ms = new MemoryStream())
                        {
                            BinaryFormatter bf = new BinaryFormatter();
                            ms.Write(buffer, 0, buffer.Length);
                            ms.Position = 0;
                            Weapon weapon = (Weapon)bf.Deserialize(ms);
                            pl.CurrentWeapon = weapon;
                        }
                    }
                    if (player.inventory != null)
                    {
                        byte[] buffer = player.inventory;
                        using (MemoryStream ms = new MemoryStream())
                        {
                            BinaryFormatter bf = new BinaryFormatter();
                            ms.Write(buffer, 0, buffer.Length);
                            ms.Position = 0;
                            Inventory inventory = (Inventory)bf.Deserialize(ms);
                            pl.Inventory = inventory;
                        }
                    }
                    //byte[] buffer 
                    using (MemoryStream ms = new MemoryStream())
                    {
                        BinaryFormatter bf = new BinaryFormatter();
                        bf.Serialize(ms, pl);
                        ms.Position = 0;
                        byte[] buffer = ms.ToArray();
                        om.Write(buffer.Length);
                        om.Write(buffer);
                    }
                    db.SaveChanges();//сохраняем в бд, потому что поставили режим онлайн=true игроку
                    NetOutgoingMessage tokenMessage = Server.CreateMessage();
                    tokenMessage.Write((short)ServerPacketType.AuthToken);
                    tokenMessage.Write(pl.Nick);
                    tokenMessage.Write(pl.Token.ToString());
                    Server.SendToAll(tokenMessage, NetDeliveryMethod.ReliableSequenced);//отсылаем всем серверам данные о входе игрока
                }
                else
                {
                    om.Write(false);
                }
            }
            else
            {
                om.Write(false);
            }
            msg.SenderConnection.SendMessage(om, NetDeliveryMethod.ReliableSequenced, 0);
        }

        private void countersTimer_Tick(object sender, EventArgs e)
        {
            float cpuValue = cpuCounter.NextValue() / Environment.ProcessorCount;
            CPUValues.Add(cpuValue);
            proc = Process.GetCurrentProcess();

            if (minMem > proc.PrivateMemorySize64)
            {
                minMem = proc.PrivateMemorySize64;
            }
            if (maxMem < proc.PrivateMemorySize64)
            {
                maxMem = proc.PrivateMemorySize64;
            }
            if (minCPU > cpuValue)
            {
                minCPU = cpuValue;
            }
            if (maxCPU < cpuValue)
            {
                maxCPU = cpuValue;
            }
            MemoryValues.Add(proc.PrivateMemorySize64);
            if (MemoryValues.Count > 300)
            {
                MemoryValues.RemoveAt(0);
            }
            if (CPUValues.Count > 300)
            {
                CPUValues.RemoveAt(0);
            }
            cpuMaxLbl.Text = "CPU максимум: " + maxCPU.ToString("N0") + "%";
            cpuMinLbl.Text = "CPU минимум: " + minCPU.ToString("N0") + "%";
            cpuAvLbl.Text = "CPU в среднем за 5 минут: " + CPUValues.Average().ToString("N0") + "%";
            memMaxLbl.Text = "Максимум памяти: " + GetMemoryValue(maxMem);
            memMinLbl.Text = "Минимум памяти: " + GetMemoryValue(minMem);
            memAvLbl.Text = "Памяти в среднем за 5 минут: " + GetMemoryValue((long)MemoryValues.Average());
            cpuLbl.Text = "CPU: " + cpuValue.ToString("N0") + "%";
            memLbl.Text = "Памяти занято: " + GetMemoryValue(proc.PrivateMemorySize64);
            incTrafLbl.Text = "Входящий траффик: " + GetMemoryValue(Server.Statistics.ReceivedBytes);
            outTrafLbl.Text = "Исходящий траффик: " + GetMemoryValue(Server.Statistics.SentBytes);
            totalTrafLbl.Text = "Всего: " + GetMemoryValue(Server.Statistics.SentBytes + Server.Statistics.ReceivedBytes);
            inMessagesLbl.Text = "Входящих сообщений: " + Server.Statistics.ReceivedMessages;
            outMessagesLbl.Text = "Исходящих сообщений: " + Server.Statistics.SentMessages;
            totalMessagesLbl.Text = "Всего: " + (Server.Statistics.ReceivedMessages + Server.Statistics.SentMessages);
            inPacketsLbl.Text = "Входящих пакетов: " + Server.Statistics.ReceivedPackets;
            outPacketsLbl.Text = "Исходящих пакетов: " + Server.Statistics.SentPackets;
            totalPacketsLbl.Text = "Всего: " + (Server.Statistics.ReceivedPackets + Server.Statistics.SentPackets);
            connectionsLbl.Text = "Соединений: " + Server.ConnectionsCount;

            TimeSpan TimeDiff = StopWatch.Elapsed;

            uptimeLbl.Text = string.Format("Время работы: {0:00}:{1:00}:{2:00}:{3:00}",
               TimeDiff.Days,
               TimeDiff.Hours,
               TimeDiff.Minutes,
               TimeDiff.Seconds);
            graphicPanel.Invalidate();
        }

        private void graphicPanel_Paint(object sender, PaintEventArgs e)
        {
            Graphics g = e.Graphics;
            float rectWidth = (float)graphicPanel.Width / 300.0f;
            float maxValueCPU = CPUValues.Max();
            long maxValueMem = MemoryValues.Max();
            for (int i = 0; i < MemoryValues.Count; i++)
            {
                g.FillRectangle(new SolidBrush(Color.Black), graphicPanel.Width - (float)(MemoryValues.Count - i) * rectWidth, graphicPanel.Height - ((float)MemoryValues[i] / (float)maxValueMem * (float)graphicPanel.Height), rectWidth, graphicPanel.Height);
                g.FillRectangle(new SolidBrush(Color.Red), graphicPanel.Width - (float)(MemoryValues.Count - i) * rectWidth, graphicPanel.Height - ((float)CPUValues[i] / (float)maxValueCPU * (float)graphicPanel.Height), rectWidth, graphicPanel.Height);
            }
            g.FillRectangle(new SolidBrush(Color.LightGray), graphicPanel.Width - 30, graphicPanel.Height - 15, 20, 15);
            g.FillRectangle(new SolidBrush(Color.LightGray), graphicPanel.Width - 106, 0, 94, 15);
            g.DrawString(maxValueCPU.ToString("N0") + "% / " + GetMemoryValue(maxValueMem), this.Font, new SolidBrush(Color.Black), graphicPanel.Width - 100, 0);
            g.DrawString("0", this.Font, new SolidBrush(Color.Black), graphicPanel.Width - 25, graphicPanel.Height - 15);
            g.Save();
        }
        public string GetMemoryValue(long value)
        {
            double temp;
            if (value < 1024)
            {
                return value.ToString();
            }
            if (value < 1024 * 1024)
            {
                temp = value / 1024.0d;
                return temp.ToString("N2") + "KB";
            }
            if (value < 1024 * 1024 * 1024)
            {
                temp = value / 1024.0d / 1024.0d;
                return temp.ToString("N2") + "MB";
            }
            else
            {
                temp = value / 1024.0d / 1024.0d / 1024.0d;
                return temp.ToString("N2") + "GB";
            }
        }

        private void Main_FormClosed(object sender, FormClosedEventArgs e)
        {
            AddConsoleLog("Server shutdown by user.", ConsoleMessageType.Info);
            _isExiting = true;
            Server.Shutdown("Master server shutdown");
        }
    }
}
